package umg.os.kernel;

import umg.os.kernel.scheduler.Scheduler;
import umg.os.kernel.scheduler.Scheduler.SchedulerNotAssignedException;
import umg.os.process.ProcessControlBlock;
import umg.os.process.ProcessControlBlock.ProcessFinishListener;
import umg.os.timer.Timer;
import umg.os.timer.Timer.TimerListener;

public final class Kernel implements TimerListener, ProcessFinishListener {

	private final static Kernel singletonInstance = new Kernel();
	private ProcessControlBlock processExecuting;
	
	private Kernel() {
		Timer.register(this);
	}
	
	public static Kernel getInstance() {
		return singletonInstance;
	}
	
	public static void initKernelUsing(Scheduler schedulerToUse) {
		Scheduler.setSchedulerInstance(schedulerToUse);
	}
	
	public void startExecutingProcess(ProcessControlBlock newProcessToExecute) {
		if (newProcessToExecute == null) {
			System.out.println("Nothing was allocated");
			return;
		}
		if (processExecuting != null) {
			throw new ProcessAlreadyAllocatedException("The process: "+processExecuting.name+" is already running, " +
					"the process: "+newProcessToExecute.name+" cannot be allocated.");
		} else {
			processExecuting = newProcessToExecute;
			processExecuting.allocateInCPU();
		}
	}
	
	public ProcessControlBlock stopExecutingProcess() {
		if(processExecuting!= null){
			processExecuting.removeFromCPU();
		}
		ProcessControlBlock preaviousExecutingProcess = processExecuting;
		processExecuting = null;
		return preaviousExecutingProcess;
	}
	
	public static void startSimulation() {
		try {
			Scheduler.getInstance();
		} catch (SchedulerNotAssignedException e) {
			System.err.println("Scheduler has not been set yet");
			return;
		}
		Timer.start();
	}
	
	public boolean isExecuting(){
		return processExecuting!= null;
	}
	
	public static ProcessControlBlock stopExecuting() {
		ProcessControlBlock processThatUsedToBeExecuting = singletonInstance.processExecuting;
		singletonInstance.processExecuting = null;
		return processThatUsedToBeExecuting;
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void nextTimeUnit() {
		if (processExecuting != null) {
			//System.out.println("Executing: "+processExecuting.name+
			//		"\n\t"+processExecuting.getNextOperation());
			//System.out.print(processExecuting.name+"|");
			System.out.print(processExecuting.name+":"+processExecuting.getNextOperation().type+"|");
			processExecuting.increaseInstructionPointer();
		} else {
			System.err.println("Waste...");
		}
	}

	@Override
	public void processFinished() {
		System.err.print("*");
		Dispatcher.getInstance().switchExecutingProcess();
	}
	
	@SuppressWarnings("serial")
	private class ProcessAlreadyAllocatedException extends RuntimeException {
		
		private ProcessAlreadyAllocatedException(String message) {
			super(message);
		}
		
	}
	
}
